/**
 * Copyright (C) 2009+ Magic Zhao
 *		 2008+ Spark Zheng
 *
 * @file	include/serialize.h
 * @brief	c struct serialize functions:
 *		-persistence serialize
 *			-A. phpserialize
 *			-B. xml(xml-parse)
 *			-C. json(json-c)
 *		-memory serialze
 *
 * @module      dlib
 * @see		dlib
 *
 * @history
 *	version 0.1.0, spark init serialize module
 *	version 0.2.0, spark move source-code from magic cext
 */

#ifndef _DLIB_SERIALIZE_H
#define _DLIB_SERIALIZE_H

#ifdef __cplusplus
extern "C" {
#endif

#include "global.h"
#include "str.h"
#include "mempool.h"
#include "htable.h"

/**
 * PHP serializor module,
 * support int, double, string, array in php
 * APIs: dlib_php_serialize, dlib_php_unserialize
 */
/** types can send between php/python/c&c++ */
enum dlib_php_types {
	DLIB_PHP_OTHER = -1,
	DLIB_PHP_INT = 0,
	DLIB_PHP_DOUBLE,
	DLIB_PHP_STRING,
	DLIB_PHP_MAP,
	DLIB_PHP_TYPES_NR
};

typedef enum dlib_php_types	dlib_php_types_t;
typedef struct dlib_php_double	dlib_php_double_t;
typedef struct dlib_php_string	dlib_php_string_t;
typedef struct dlib_php_mappair	dlib_php_mappair_t;
typedef struct dlib_php_map	dlib_php_map_t;

/**
 * double object in php-serialized output
 */
struct dlib_php_double {
	double	val;		/* value */
};

/**
 * string object in php-serialized output
 */
struct dlib_php_string {
	int	len;		/* length of the string */
	char	*val;		/* string value */
};

/**
 * a key->value pair in php map
 */
struct dlib_php_mappair {
	int	key_type;	/* key field */
	void	*key_ptr;	/* length of key, if key_size is 0, key field is index */
	int	val_type;	/* element type */
	void	*val_ptr;	/* element pointer */
};

/**
 * php map & array object
 */
struct dlib_php_map {
	int			capacity;	/* total child number the map can hold */
	htable_naive_t		*map;		/* a hash table for key->value search */
	dlib_alloc_t		*allocator;	/* the naive allocator used for this map */
	int			child_count;	/* current child number in the map */
	dlib_php_mappair_t	child_list[0];	/* child list in map */
};

#define DLIB_PMAP_SIZE(size) \
	(sizeof(dlib_php_map_t) + (sizeof(dlib_php_mappair_t) * (size)))

/** php_serialize for map, support buf is enough */
extern int php_serialize(dlib_php_map_t *mp, char *buf, size_t buf_len);

/** php_unserialize for map, support na is enough space for mp */
extern int php_unserialize(char *buf, size_t buf_len,
			   dlib_php_map_t **mp, dlib_alloc_t *na);

/** php serialize get items APIs */
/**
 * php_decode_get_type
 * @brief decode type from a serialized string,
 *	  the first byte in php serailized string is type field
 * @return type of current object, -1 if not found
 * @param encoded_str serialized
 */
static inline int php_decode_get_type(char *encoded_str)
{
	switch (encoded_str[0]) {
		case 'i': return DLIB_PHP_INT;
		case 'I': return DLIB_PHP_INT;
		case 'd': return DLIB_PHP_DOUBLE;
		case 'D': return DLIB_PHP_DOUBLE;
		case 's': return DLIB_PHP_STRING;
		case 'S': return DLIB_PHP_STRING;
		case 'a': return DLIB_PHP_MAP;
		case 'A': return DLIB_PHP_MAP;
		default : return DLIB_PHP_OTHER;
	}
}

/**
 * php_map_get
 * @brief find a item from key in the map
 * @return 0 found, -1 not found
 * @param key_type type of key
 * @param key a dlib_php_mappair_t object
 * @param map map object
 * @param pair[out] return value of found pair
 */
static inline int php_map_get(dlib_php_map_t *map, int key_type,
			      void *key, dlib_php_mappair_t **pair)
{
	long *value;
	dlib_php_mappair_t tmppair;

	tmppair.key_ptr = (void *)key;
	tmppair.key_type = key_type;
	tmppair.val_ptr = NULL;
	tmppair.val_type = DLIB_PHP_OTHER;

	if (map->map == NULL) {
		return -1;
	}

	if (ht_naive_get(map->map, &tmppair, &value) == 0) {
		*pair = (dlib_php_mappair_t *)value;
		return 0;
	}
	return -1;
}

/** php map functions */
static inline int map_get_int_by_string(dlib_php_map_t *map,
					dlib_php_string_t *key, int *value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_STRING, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_INT) {
		*value = (int)pair->val_ptr;
		return 0;
	}
	return -1;
}

static inline int map_get_string_by_string(dlib_php_map_t *map,
					   dlib_php_string_t *key,
					   dlib_php_string_t **value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_STRING, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_STRING) {
		*value = (dlib_php_string_t *)pair->val_ptr;
		return 0;
	}
	return -1;
}

static inline int map_get_double_by_string(dlib_php_map_t *map,
					   dlib_php_string_t *key,
					   double *value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_STRING, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_DOUBLE) {
		dlib_php_double_t *dv = (dlib_php_double_t *)pair->val_ptr;
		if (dv != NULL) {
			*value = dv->val;
			return 0;
		}
	}
	return -1;
}

static inline int map_get_map_by_string(dlib_php_map_t *map,
					dlib_php_string_t *key,
					dlib_php_map_t **value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_STRING, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_MAP) {
		if (pair->val_ptr != NULL) {
			*value = (dlib_php_map_t *)pair->val_ptr;
			return 0;
		}
	}
	return -1;
}

static inline int map_get_int_by_index(dlib_php_map_t *map, int key, int *value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_INT, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_INT) {
		*value = (int)pair->val_ptr;
		return 0;
	}
	return -1;
}

static inline int map_get_string_by_index(dlib_php_map_t *map,
					  int key, dlib_php_string_t **value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_INT, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_STRING) {
		*value = (dlib_php_string_t *)pair->val_ptr;
		return 0;
	}
	return -1;
}

static inline int map_get_double_by_index(dlib_php_map_t *map,
					  int key, double *value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_INT, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_DOUBLE) {
		dlib_php_double_t *dv = (dlib_php_double_t *)pair->val_ptr;
		if (dv != NULL) {
			*value = dv->val;
			return 0;
		}
	}
	return -1;
}

static inline int map_get_map_by_index(dlib_php_map_t *map,
				       int key, dlib_php_map_t **value)
{
	dlib_php_mappair_t *pair;
	if (php_map_get(map, DLIB_PHP_INT, (void *)key, &pair) == 0 &&
			pair->val_type == DLIB_PHP_MAP) {
		if (pair->val_ptr != NULL) {
			*value = (dlib_php_map_t *)pair->val_ptr;
			return 0;
		}
	}
	return -1;
}

/** php serialize set items APIs */
/**
 * php_alloc_map
 * allocate a dlib_php_map_t with given capacity
 * @param capacity the capacity property of the map
 * @param na memory allocator
 * @return the allocated map sturct or NULL
 */
extern dlib_php_map_t *php_alloc_map(dlib_alloc_t *na, int capacity);

static inline dlib_php_string_t *php_alloc_string(dlib_alloc_t *na,
						  int str_len, char *str)
{
	dlib_php_string_t *ptr = 
		(dlib_php_string_t *)dlib_naive_alloc(na, sizeof(*ptr));
	if (ptr != NULL) {
		ptr->val = str;
		ptr->len = str_len;

	}
	return ptr;
}

static inline dlib_php_double_t *php_alloc_double(dlib_alloc_t *na, double value)
{
	dlib_php_double_t *ptr =
		(dlib_php_double_t *)dlib_naive_alloc(na, sizeof(*ptr));
	if (ptr != NULL) {
		ptr->val = value;
	}
	return ptr;
}

/**
 * php_realloc_map
 * @brief reallocate a dlib_php_map_t to other capacity
 * @return the reallocated map struct or NULL
 * @param om original dlib_php_map_t pointer
 * @param new_capacity target capacity
 * @param na memory allocator
 */
static inline dlib_php_map_t *php_realloc_map(dlib_alloc_t *na,
					      dlib_php_map_t *om, int new_capacity)
{
	dlib_php_map_t *nmp =
		(dlib_php_map_t *)dlib_naive_alloc(na, DLIB_PMAP_SIZE(new_capacity));
	if (nmp != NULL) {
		memcpy(nmp, om, DLIB_PMAP_SIZE(om->child_count));
		nmp->capacity = new_capacity;
	}
	return nmp;
}

/**
 * php_map_set
 * @brief insert a key value pair to map
 * @return 0, success; -1 failed
 * @param key key field
 * @param key_type type of key field , only DLIB_PHP_INT & DLIB_PHP_STRING are valid now
 * @param type type of value field
 * @param ptr value field pointer
 * @param mp map phpointer
 * @param na memory allocator
 */
static inline int php_map_set(dlib_php_map_t **mp, dlib_alloc_t *na,
			      int key_type, void *key, int type, void *ptr)
{
	dlib_php_map_t *curr_map = *mp;

	if (curr_map->capacity <= curr_map->child_count) {
		dlib_php_map_t *nmp = php_realloc_map(na, curr_map,
						      (curr_map->capacity << 1));
		if (nmp) {
			curr_map = nmp;
			*mp = nmp;
		} else {
			return -1;
		}
	}

	curr_map->child_list[curr_map->child_count].key_ptr = key;
	curr_map->child_list[curr_map->child_count].key_type = key_type;
	curr_map->child_list[curr_map->child_count].val_ptr = ptr;
	curr_map->child_list[curr_map->child_count].val_type = type;
	ht_naive_insert(curr_map->map, &curr_map->child_list[curr_map->child_count],
		  &curr_map->child_list[curr_map->child_count]);
	curr_map->child_count++;

	return 0;
}

static inline int map_set_int_by_index(dlib_php_map_t **mp, dlib_alloc_t *na,
				       int idx, int value)
{
	return php_map_set(mp, na, DLIB_PHP_INT,
			   (void *)idx, DLIB_PHP_INT, (void *)value);
}

static inline int map_set_string_by_index(dlib_php_map_t **mp, dlib_alloc_t *na,
					  int idx, dlib_php_string_t *str)
{
	return php_map_set(mp, na, DLIB_PHP_INT, (void *)idx, DLIB_PHP_STRING, str);
}

static inline int map_set_double_by_index(dlib_php_map_t **mp, dlib_alloc_t *na,
					  int idx, dlib_php_double_t *value)
{
	return php_map_set(mp, na, DLIB_PHP_INT,
			   (void *)idx, DLIB_PHP_DOUBLE, (void *)value);
}

static inline int map_set_map_by_index(dlib_php_map_t **mp, dlib_alloc_t *na,
					  int idx, dlib_php_map_t *value)
{
	return php_map_set(mp, na, DLIB_PHP_INT,
			   (void *)idx, DLIB_PHP_MAP, (void *)value);
}

static inline int map_set_int_by_string(dlib_php_map_t **mp, dlib_alloc_t *na,
					dlib_php_string_t *key, int value)
{
	return php_map_set(mp, na, DLIB_PHP_STRING,
			   (void *)key, DLIB_PHP_INT, (void *)value);
}

static inline int map_set_string_by_string(dlib_php_map_t **mp,
					   dlib_alloc_t *na,
					   dlib_php_string_t *key,
					   dlib_php_string_t *value)
{
	return php_map_set(mp, na, DLIB_PHP_STRING,
			   (void *)key, DLIB_PHP_STRING, (void *)value);
}

static inline int map_set_double_by_string(dlib_php_map_t **mp,
					   dlib_alloc_t *na,
					   dlib_php_string_t *key,
					   dlib_php_double_t *value)
{
	return php_map_set(mp, na, DLIB_PHP_STRING,
			   (void *)key, DLIB_PHP_DOUBLE, (void *)value);
}

static inline int map_set_map_by_string(dlib_php_map_t **mp,
					dlib_alloc_t *na,
					dlib_php_string_t *key,
					dlib_php_map_t *value)
{
	return php_map_set(mp, na, DLIB_PHP_STRING,
			   (void *)key, DLIB_PHP_MAP, (void *)value);
}

/** php serialize APIs */
/**
 * php_encode_string
 * @brief serialize a char array into php string format
 * @return the length of serialized string, 0 if error occurs
 * @param str a p_string_t object that allocated before
 * @param buf[out] output buffer
 * @param buf_len output buffer size
 */
extern int php_encode_string(dlib_php_string_t *str, char *buf, size_t buf_len);

/**
 * php_encode_double
 * @brief serialize double struct into string format
 * @return length of serialized string
 * @param dbt dobule object
 * @param buf output buffer
 * @param buf_len output buffer size
 */
extern int php_encode_double(dlib_php_double_t *dbt, char *buf, size_t buf_len);

/**
 * php_encode_int
 * @brief serialize integer struct into string format
 * @return length of serialized string
 * @param val integer value
 * @param buf output buffer
 * @param buf_len output buffer size
 */
extern int php_encode_int(int value, char *buf, size_t buf_len);

/**
 * php_encode_map
 * @brief serialize map struct into string format
 * @return length of serialized string, 0 if error occurs
 * @param mp map object
 * @param buf output buffer
 * @param buf_len output buffer size
 */
extern int php_encode_map(dlib_php_map_t *mp, char *buf, size_t buf_len);

/**
 * php_encode_map_with_length
 * @brief serialize map struct into string format, first 8 bytes are %08d string
 * @return length of serialized string, 0 if error occurs
 * @param mp map object
 * @param buf output buffer
 * @param buf_len output buffer size
 */
extern int php_encode_map_with_length(dlib_php_map_t *mp, char *buf, size_t buf_len);


/**
 * php_decode_int
 * @brief decode a seialized string into int value,
 *	  the encoded string may not NULL terminated, so encoded_len is needed
 * @return the length of encoded integer in encoded_str, 0 if failed
 * @param encoded_str the encoded string buffer
 * @param encoded_len length of the encoded string
 * @param val output integer value
 */
extern int php_decode_int(char *encoded_str, int encoded_len, int *val, dlib_alloc_t *na);

/**
 * php_decode_double
 * @brief decode a seialized string into double struct,
 *	  the encoded string may not NULL terminated, so encoded_len is needed
 * @return the length of encoded double in encoded_str, 0 if failed
 * @param encoded_str the encoded string buffer
 * @param encoded_len length of the encoded string
 * @param dbl output double struct
 */
extern int php_decode_double(char *encoded_str, int encoded_len,
			     dlib_php_double_t *dbl, dlib_alloc_t *na);

/**
 * php_decode_string
 * @brief decode a seialized string into string struct,
 *	  the encoded string may not NULL terminated, so encoded_len is needed
 * @return the length of encoded string in encoded_str, 0 if failed
 * @param encoded_str the encoded string buffer
 * @param encoded_len length of the encoded string
 * @param str output string object, only the pointer to string buffer is copyed,
 *	  so encoded_str cannot be changed
 */
extern int php_decode_string(char *encoded_str, int encoded_len,
			     dlib_php_string_t *str, dlib_alloc_t *na);

/**
 * php_decode_map
 * @brief decode serialized string into map object,
 *	  by recurrence decode every child of the map
 * @return the length of encoded string in encoded_str, 0 if failed
 * @param encoded_str the encoded string buffer
 * @param encoded_len length of the encoded string
 * @param mp output map object
 * @param na memory allocator
 */
extern int php_decode_map(char *encoded_str, int encoded_len,
			  dlib_php_map_t **mp, dlib_alloc_t *na);



#ifdef __cplusplus
}
#endif
#endif
